package com.guli.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guli.common.constant.RedisConstant;
import com.guli.common.utils.Type;
import com.guli.product.dao.AttrAttrgroupRelationDao;
import com.guli.product.dao.AttrGroupDao;
import com.guli.product.dao.CategoryBrandRelationDao;
import com.guli.product.dao.CategoryDao;
import com.guli.product.entity.AttrAttrgroupRelationEntity;
import com.guli.product.entity.AttrGroupEntity;
import com.guli.product.entity.CategoryBrandRelationEntity;
import com.guli.product.entity.CategoryEntity;
import com.guli.product.service.CategoryService;
import com.guli.product.vo.Catelog2Vo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationDao categoryBrandRelationDao;
    @Autowired
    private AttrGroupDao attrGroupDao;
    @Autowired
    private AttrAttrgroupRelationDao attrAttrgroupRelationDao;
    @Autowired
    private CategoryDao categoryDao;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<CategoryEntity> listWithTree() {
        //缓存中有数据，则直接返回数据
        Object categoriesRedis = redisTemplate.opsForValue().get(RedisConstant.CATEGORY_LIST);
        if (categoriesRedis != null) {
            List<CategoryEntity> categoryEntityList = JSON
                    .parseObject(categoriesRedis.toString(), new TypeReference<List<CategoryEntity>>() {});
            return categoryEntityList;
        }
        //查询所有分类信息
        List<CategoryEntity> categories = baseMapper.selectList(null);
        List<CategoryEntity> categoryEntities = categories.stream().filter(category1 -> category1.getParentCid() == 0)
                .peek(category1 -> category1.setChildren(getChildren(category1, categories)))
                .sorted(Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))
                .collect(Collectors.toList());
        redisTemplate.opsForValue().set(RedisConstant.CATEGORY_LIST, categoryEntities);
        return categoryEntities;
    }

    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        return all.stream().filter(categoryEntity2 -> categoryEntity2.getParentCid().equals(root.getCatId()))
                .peek(categoryEntity3 -> categoryEntity3.setChildren(getChildren(categoryEntity3, all)))
                .sorted(Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))
                .collect(Collectors.toList());
    }

    @Override
    public void updateRelationCategory(CategoryEntity categoryEntity) {
        //更新分类表中的信息
        baseMapper.updateById(categoryEntity);
        //更新与该分类中关联的信息
        categoryBrandRelationDao.updateRelationCategory(categoryEntity.getCatId(), categoryEntity.getName());
    }

    @Override
    public void removeByIdsWithRelation(List<Long> ids) {
        //删除分类表中信息
        baseMapper.deleteBatchIds(ids);
        //删除与该分类中关联的信息
        categoryBrandRelationDao.delete(new QueryWrapper<CategoryBrandRelationEntity>()
                .in("catelog_id", ids));
        List<AttrGroupEntity> attrGroupEntities = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>()
                .eq("catelog_id", ids));
        attrGroupDao.delete(new QueryWrapper<AttrGroupEntity>()
                .in("catelog_id", ids));
        List<Long> attGroupList = attrGroupEntities.stream().map(AttrGroupEntity::getAttrGroupId).collect(Collectors.toList());
        attrAttrgroupRelationDao.delete(new QueryWrapper<AttrAttrgroupRelationEntity>()
                .in("attr_group_id", attGroupList));
    }

    @Override
    public List<CategoryEntity> getLevel1Catgories() {
        return baseMapper
                .selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    //TODO 内存溢出
    /*
     * 缓存穿透： 指查询一个一定不存在的数据，由于缓存是不命中，将去查询数据库
     * 解决方案：null结果缓存，并加入短暂过期时间
     * 缓存雪崩：缓存雪崩是指在我们设置缓存时key采用了相同的过期时间，导致缓存在某一时刻同时失效，请求全部转发到DB，DB瞬间压力过重导致雪崩
     * 解决方案：在原有失效时间基础上加一个随机值，比如1-5分钟
     * 缓存击穿：对于一些设置了过期时间的key，如果这些key可能会在某个时间点被高并发地访问，是一个非常热点的数据，
     * 如果这个key在大量请求同时进来前正好失效，那么所有对这个key的查询数据都落到db，我们称为缓存击穿
     * 解决方案：加锁，大量并发只让一个人去查，其他人等待，查到以后释放锁，其他人获取到锁，先查缓存，就会有数据，不用去查询数据库
     * */
    public Map<String, List<Catelog2Vo>> getCatelogFromWithRedisson() {
        //根据锁名称生成分布式锁
        RLock lock = redissonClient.getLock("lock");
        //初始化categoryVoMap为null
        Map<String, List<Catelog2Vo>> categoryVoMap = null;
        //添加锁
        try {
            lock.lock();
            String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
            if (!StringUtils.isEmpty(categoryJson)) {
                //缓存有数据，直接返回数据
                categoryVoMap = (Map<String, List<Catelog2Vo>>) JSON.parseObject(categoryJson, Map.class);
                log.info("命中缓存，直接返回数据......");
            } else {
                log.info("未命中缓存，查询了数据库......");
                //查询数据库
                categoryVoMap = getCatelog();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return categoryVoMap;
    }

    public synchronized Map<String, List<Catelog2Vo>> getCatelog() {
        //查询所有分类
        List<CategoryEntity> allCategories = categoryDao.selectList(null);
        //查询所有一级分类
        List<CategoryEntity> categoryies1 = allCategories.stream().filter(c -> c.getParentCid() == 0).collect(Collectors.toList());
        Map<String, List<Catelog2Vo>> categoryVoMap = categoryies1.stream().collect(Collectors.toMap(k1 -> k1.getCatId().toString(), v1 -> {
            //查询每一个一级分类下的二级分类
            List<CategoryEntity> categories2 = allCategories.stream().filter(c -> c.getParentCid().equals(v1.getCatId())).collect(Collectors.toList());
            List<Catelog2Vo> catelog2Vos = null;
            if (!CollectionUtils.isEmpty(categories2)) {
                catelog2Vos = categories2.stream().map(v2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString(), null, v2.getCatId().toString(), v2.getName());
                    //查询该二级分类下的三级分类
                    List<CategoryEntity> categories3 = allCategories.stream().filter(c -> c.getParentCid().equals(v2.getCatId())).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(categories3)) {
                        List<Catelog2Vo.Category3Vo> category3Vos = categories3.stream().map(v3 ->
                                new Catelog2Vo.Category3Vo(v2.getCatId().toString(), v3.getCatId().toString(), v3.getName())
                        ).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(category3Vos);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        String categoryJson = JSONObject.toJSONString(categoryVoMap);
        stringRedisTemplate.opsForValue().set("categoryJson", categoryJson, 1, TimeUnit.DAYS);
        log.info("将数据存入缓存中......");
        return categoryVoMap;
    }
}
